Skip to content

Conversation

@stacode123
Copy link

Implements a cache for train collision checks for better performance
Before: https://spark.lucko.me/cgjtsUl25f
After: https://spark.lucko.me/p7ZcDxAUFA
Criticism welcome.
Made with the help of Copilot

World used for testing :
world.zip

@sleepy-evelyn
Copy link

sleepy-evelyn commented Nov 8, 2025

If this works properly this is brilliant @stacode123 and definately needed. We've turned train collisions off entirely to help with lag but thats definately not a fix most servers want to implement.

Using Copilot is a little worrying though because you need to know exactly whats going on for this. Make sure you've tested it thoroughly and used the debugger to step through the code to see whats going on and if it aligns with what you expect.

@Jozufozu Jozufozu self-requested a review November 8, 2025 21:19
Copy link
Contributor

@Jozufozu Jozufozu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very excited about this and the spark profile is extremely promising. A 5mspt difference is huge

Image Image

How many trains did you use in your test world? It would be helpful to upload a screenshot of the chaos 😆

Also after the requested changes and before this merges I'd like to request a longer profile to compare. I'm a little nervous the 16 second profiles may not have enough signal to noise

if (length > 0.0001) {
direction[index] = diff.normalize();
} else {
direction[index] = Vec3.ZERO;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How will this happen?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you've removed this but please justify it

@stacode123
Copy link
Author

test world has 180 trains more than that would have caused my computer to have a seizure
image
Will address the comments

@stacode123
Copy link
Author

Performance impact on a server with 475 trains:

image-1.webp

Before:
image-21.png

After:
image-24.png

@sleepy-evelyn
Copy link

sleepy-evelyn commented Nov 9, 2025

To be honest it's rare u see such a stark contrast between before and after. A similar number of players are connected to the server as well so the difference likely isn't down to that. :)

@stacode123
Copy link
Author

@Jozufozu Please review the changes :D


Vec3 start = (speed < 0 ? trailingPoint : leadingPoint).getPosition(graph);
Vec3 end = (speed < 0 ? leadingPoint : trailingPoint).getPosition(graph);
Vec3 start = collisionCache.start[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did the speed < 0 checks go? If the train is moving backwards we need to check for collisions with the trailing end as if it were the head

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh also do we know for sure that the collision cache is non-null here?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. That is handled when collision cache is created.
  2. Earlier in the train tick we updated the collision cache but i can add a null check just to be sure

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I do not see anything in this function that would indicate the order of the cache is reversed based on speed https://github.com/Creators-of-Create/Create/pull/9513/files#diff-671a8e994774f6be471b30fd8459587f56fd246119f83bb82b47b47e0056608cR646
  2. Earlier in the train tick we potentially invalidate the cache, leaving it null

if (length > 0.0001) {
direction[index] = diff.normalize();
} else {
direction[index] = Vec3.ZERO;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you've removed this but please justify it

Comment on lines +222 to +224
for (Train train : movingTrains){
train.updateCollisionCache();
train.earlyTick(level);}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatting please

for (Train train : movingTrains) {
	train.updateCollisionCache();
	train.earlyTick(level);
}

do we not need the collision cache for waiting trains? do we not expect moving trains to collide with trains that are stationary?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wont stationary trains have already built up cache ? And trying to calculate it again would just yield the same result?

Copy link
Contributor

@Jozufozu Jozufozu Nov 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about on initial level load? Can we really assume that a train that just became stationary this tick will have valid positions in the cache from the previous tick where it was moving?

Still, that's a good point. If you're feeling ambition you could probably check some easy to compute value at the start of updateCollisionCache to see if the train has moved from the previous tick and decide whether to update the collision cache. If you do though, I'd like to see proof that it works and waiting trains still get collided with

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 Initial level load will have the cache empty, causing it to update at the start of the collision checks.
2. I think so? we have been running the build on our server with no issues for now.
3. That would the best direction

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Where will it update? On this line you specifically skip trains that do not have a collision cache: https://github.com/Creators-of-Create/Create/pull/9513/files#diff-671a8e994774f6be471b30fd8459587f56fd246119f83bb82b47b47e0056608cR732
  2. I believe it doesn't crash or anything, but I'm not convinced that there aren't false negatives in the collision checks under the current iteration of this PR
  3. I agree, do it

if (derailed || graph == null) {
if (collisionCache != null)
collisionCache.invalidate();
collisionCache = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

probably don't need the collisionCache != null check anymore. just assigning is fine

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you mean by that

private void updateCollisionCache() {
if (derailed || graph == null) {
if (collisionCache != null)
collisionCache.invalidate();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a comment in the code please

@stacode123 stacode123 requested a review from Jozufozu November 16, 2025 12:48
//
if (derailed || graph == null) {
if (collisionCache != null)
collisionCache = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we still be setting this to null before returning?
This is why I asked you to explain why it was being set to null here, so that future maintainers can understand the intention.

My understanding of your intention is that a null collision cache means there is nothing valid to collide with. Here you explicitly leave a cache with potentially stale positions in a valid state



public void updateCollisionCache() {
//
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to write something here?

collisionCache = new CollisionCache(maxExpectedSegments);
}

//For adding segments between carriages
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

// For adding segments between carriages
Leave a space after the // please

continue;
if (train.graph != null && train.graph != graph)
continue;
if (train.collisionCache == null) {continue;}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Formatting

if (train.collisionCache == null)
    continue;

or

if (train.collisionCache == null) {
    continue;
}

but be consistent

Comment on lines +222 to +224
for (Train train : movingTrains){
train.updateCollisionCache();
train.earlyTick(level);}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Where will it update? On this line you specifically skip trains that do not have a collision cache: https://github.com/Creators-of-Create/Create/pull/9513/files#diff-671a8e994774f6be471b30fd8459587f56fd246119f83bb82b47b47e0056608cR732
  2. I believe it doesn't crash or anything, but I'm not convinced that there aren't false negatives in the collision checks under the current iteration of this PR
  3. I agree, do it


Vec3 start = (speed < 0 ? trailingPoint : leadingPoint).getPosition(graph);
Vec3 end = (speed < 0 ? leadingPoint : trailingPoint).getPosition(graph);
Vec3 start = collisionCache.start[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. I do not see anything in this function that would indicate the order of the cache is reversed based on speed https://github.com/Creators-of-Create/Create/pull/9513/files#diff-671a8e994774f6be471b30fd8459587f56fd246119f83bb82b47b47e0056608cR646
  2. Earlier in the train tick we potentially invalidate the cache, leaving it null


Vec3 start = (speed < 0 ? trailingPoint : leadingPoint).getPosition(graph);
Vec3 end = (speed < 0 ? leadingPoint : trailingPoint).getPosition(graph);
if (collisionCache == null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants